#include "server.h"

std::map<int, std::string> clientMap;

void connectDataBase()
{
    std::ifstream inFile("/home/shymer123/QtProjects/Messenger/Server/database.txt");
    std::string host, dataBaseName, userName, password;

    inFile >> host >> dataBaseName >> userName >> password;

    inFile.close();

    QString hostStr, dataBaseNameStr, userNameStr, passwordStr;
    hostStr = QString::fromStdString(host);
    dataBaseNameStr = QString::fromStdString(dataBaseName);
    userNameStr = QString::fromStdString(userName);
    passwordStr = QString::fromStdString(password);

    QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL");
    db.setHostName(hostStr);
    db.setDatabaseName(dataBaseNameStr);
    db.setUserName(userNameStr);
    db.setPassword(passwordStr);

    if(!db.open())
    {
        std::cout << "ERROR: The database could not be opened" << std::endl;
        return;
    }
}

void createSocket()
{
    SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, 0);

    if(serverSocket < 0)
    {
        std::cout << "ERROR: Failed to create a socket" << std::endl;
        exit(1);
    }

    std::cout << "SERVER: The socket was created successfully" << std::endl;

    fcntl(serverSocket, F_SETFL, O_NONBLOCK);

    sockaddr_in serverAdress;
    serverAdress.sin_family = AF_INET;
    serverAdress.sin_port = htons(PORT);
    serverAdress.sin_addr.s_addr = INADDR_ANY;

    int bindSocket = bind(serverSocket, reinterpret_cast<struct sockaddr*>(&serverAdress), sizeof(serverAdress));

    if(bindSocket < 0)
    {
        std::cout << "ERROR: Failed to bind socket" << std::endl;
        exit(2);
    }

    std::cout << "SERVER: The socket binding was successful" << std::endl;

    int listenSocket = listen(serverSocket, SOMAXCONN);

    if(listenSocket < 0)
    {
        std::cout << "ERROR: Failed to listen to the socket" << std::endl;
        exit(3);
    }

    std::cout << "SERVER: Socket listening has been successfully completed. PORT: " << PORT << std::endl;

    fd_set masterSet, readSet;
    FD_ZERO(&masterSet);
    FD_ZERO(&readSet);

    FD_SET(serverSocket, &masterSet);
    int maxFd = serverSocket;

    std::set<int> clients;

    while(true)
    {
        readSet = masterSet;

        int activity = select(maxFd + 1, &readSet, nullptr, nullptr, nullptr);
        if(activity < 0 && errno != EINTR)
        {
            std::cout << "ERROR: Failed to select a socket" << std::endl;
            break;
        }

        std::cout << "SERVER: The socket is selected successfully" << std::endl;

        if(FD_ISSET(serverSocket, &readSet))
        {
            struct sockaddr_in clientAdress;
            socklen_t clientAdressSize = sizeof(clientAdress);
            int clientSocket = accept(serverSocket, reinterpret_cast<struct sockaddr*>(&clientAdress), &clientAdressSize);
            if(clientSocket < 0)
            {
                std::cout << "ERROR: Connection could not be accepted" << std::endl;
                continue;
            }

            std::cout << "SERVER: The connection was successfully accepted" << std::endl;

            fcntl(clientSocket, F_SETFL, O_NONBLOCK);
            FD_SET(clientSocket, &masterSet);
            if(clientSocket > maxFd)
            {
                maxFd = clientSocket;
            }

            clients.insert(clientSocket);
            std::cout << "SERVER: New client connected " << inet_ntoa(clientAdress.sin_addr) << ":" << ntohs(clientAdress.sin_port) << std::endl;
        }

        std::set<int>::iterator it;
        for (it = clients.begin(); it != clients.end();)
        {
            int clientSock = *it;
            if (FD_ISSET(clientSock, &readSet))
            {
                char buffer[BUFFER_SIZE];
                int bytesRead = recv(clientSock, buffer, sizeof(buffer) - 1, 0);
                if (bytesRead > 0)
                {
                    buffer[bytesRead] = '\0';
                    std::cout << "Received query: " << buffer << std::endl;

                        QString queryStr(buffer);
                        QString result;
                        sqlQuery(queryStr, result, clientSock);

                        std::string resultStr = result.toStdString();
                        send(clientSock, resultStr.c_str(), resultStr.length(), 0);
                }
                else if (bytesRead == 0)
                {
                    std::cout << "SERVER: Client disconnected" << std::endl;
                    close(clientSock);
                    FD_CLR(clientSock, &masterSet);
                    it = clients.erase(it);
                    removeClient(clientSock);
                    continue;
                }
                else if (bytesRead < 0 && (errno != EWOULDBLOCK || errno != EAGAIN))
                {
                    std::cout << "ERROR: The message could not be received" << std::endl;
                    close(clientSock);
                    FD_CLR(clientSock, &masterSet);
                    it = clients.erase(it);
                    removeClient(clientSock);
                    continue;
                }
            }
            ++it;
        }
    }
    close(serverSocket);
}

void sqlQuery(const QString query, QString &result, int clientSock)
{
    QStringList listStr = query.split(':');
    if(listStr[0] == "update_chat_list")
    {
        notifyClients(listStr[1].toStdString());
    }
    else if(listStr[0] == "add_user")
    {
        addClient(clientSock, listStr[1].toStdString());
    }
    else
    {
        QSqlQuery querySql;
        if(!querySql.exec(query))
        {
            std::cout << "ERROR: The request could not be completed";
            exit(4);
        }
        QSqlRecord rec = querySql.record();
        int numColumns = rec.count();

        while(querySql.next())
        {
            for(int i = 0; i < numColumns; ++i)
            {
                result += querySql.value(i).toString();
                if(i < numColumns - 1)
                {
                    result += ", ";
                }
            }
            result += "\n";
        }
    }
}

void addClient(int clientSock, const std::string nickName)
{
    clientMap[clientSock] = nickName;
}

void removeClient(int clientSock)
{
    clientMap.erase(clientSock);
}

void notifyClients(const std::string& nickname)
{
    for (const auto& client : clientMap)
    {
        if (client.second == nickname)
        {
            std::string message = "update";
            send(client.first, message.c_str(), message.length(), 0);
        }
    }
}
